Skip to main content

Coin Change

LeetCode 322 | Difficulty: Medium​

Problem Description​

You are given an integer array coins representing coins of different denominations and an integer amount representing a total amount of money.

Return the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.

You may assume that you have an infinite number of each kind of coin.

Constraints​

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 2^31 - 1
  • 0 <= amount <= 10^4

Examples​

Example 1:

Input: coins = [1,2,5], amount = 11
Output: 3
Explanation: 11 = 5 + 5 + 1

Example 2:

Input: coins = [2], amount = 3
Output: -1
Explanation: Cannot make 3 with only coin denomination 2.

Example 3:

Input: coins = [1], amount = 0
Output: 0
Explanation: No coins needed for amount 0.

Approach​

This is a classic unbounded knapsack problem solvable with dynamic programming.

Key Insight: For each amount i, the minimum coins needed is: $$dp[i] = \min(dp[i - coin_j] + 1) \text{ for all coins } j$$

DP State Definition:

  • dp[i] = minimum number of coins needed to make amount i
  • Base case: dp[0] = 0 (0 coins needed for amount 0)

DP Table for Example 1 (coins = [1, 2, 5], amount = 11):

Amount01234567891011
dp[i]011221223323

Trace for dp[11]:

  • Using coin 1: dp[10] + 1 = 2 + 1 = 3
  • Using coin 2: dp[9] + 1 = 3 + 1 = 4
  • Using coin 5: dp[6] + 1 = 2 + 1 = 3
  • Minimum: 3 (5 + 5 + 1)

Complexity Analysis​

  • Time Complexity: $O(S \times N)$ β€” where $S$ is the amount and $N$ is the number of coin denominations
  • Space Complexity: $O(S)$ β€” for the DP array

public int CoinChange(int[] coins, int amount)
{
if (coins.Length == 0) return -1;
if (amount == 0) return 0;

int[] dp = new int[amount + 1];
Array.Fill(dp, amount + 1); // Use amount+1 as "infinity"
dp[0] = 0;

for (int i = 1; i <= amount; i++)
{
foreach (int coin in coins)
{
if (coin <= i && dp[i - coin] != amount + 1)
{
dp[i] = Math.Min(dp[i], dp[i - coin] + 1);
}
}
}

return dp[amount] > amount ? -1 : dp[amount];
}

Solution: Top-Down DP with Memoization​

public int CoinChange(int[] coins, int amount)
{
if (amount == 0) return 0;
int[] memo = new int[amount + 1];
Array.Fill(memo, -2); // -2 means not computed
return CoinChangeHelper(coins, amount, memo);
}

private int CoinChangeHelper(int[] coins, int amount, int[] memo)
{
if (amount < 0) return -1;
if (amount == 0) return 0;
if (memo[amount] != -2) return memo[amount];

int minCoins = int.MaxValue;
foreach (int coin in coins)
{
int result = CoinChangeHelper(coins, amount - coin, memo);
if (result >= 0)
{
minCoins = Math.Min(minCoins, result + 1);
}
}

memo[amount] = (minCoins == int.MaxValue) ? -1 : minCoins;
return memo[amount];
}

Solution: BFS Approach​

Think of it as finding the shortest path from amount to 0:

public int CoinChangeBFS(int[] coins, int amount)
{
if (amount == 0) return 0;

Queue<int> queue = new Queue<int>();
HashSet<int> visited = new HashSet<int>();
queue.Enqueue(amount);
visited.Add(amount);
int level = 0;

while (queue.Count > 0)
{
level++;
int size = queue.Count;

for (int i = 0; i < size; i++)
{
int curr = queue.Dequeue();

foreach (int coin in coins)
{
int next = curr - coin;
if (next == 0) return level;
if (next > 0 && !visited.Contains(next))
{
visited.Add(next);
queue.Enqueue(next);
}
}
}
}

return -1;
}

Interview Tips​

  • Why Bottom-Up over Top-Down? β€” Avoids recursion stack overhead; better cache locality
  • Greedy doesn't work! β€” For coins = [1, 3, 4] and amount = 6, greedy gives 4+1+1 (3 coins) but optimal is 3+3 (2 coins)
  • Edge Cases:
    • amount = 0 β†’ return 0
    • No valid combination β†’ return -1
    • Single coin equals amount β†’ return 1